/******************************************************************************
*  A Teaching GA					  Developed by Hal Stringer & Annie Wu, UCF
*  Version 2, January 18, 2004
*******************************************************************************/

import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class IPD extends FitnessFunction
{

/*******************************************************************************
*                            INSTANCE VARIABLES                                *
*******************************************************************************/
	enum EvaluationMethod {METHOD_1, METHOD_2};
	
	public boolean isIndividualTournaments;
	public int tournamentRounds;
	public int pdStepCount;
	public Map<Strategy, Double> staticStratToBestMap;
	public EvaluationMethod evaluationMethod;
/*******************************************************************************
*                            STATIC VARIABLES                                  *
*******************************************************************************/

/*******************************************************************************
*                              CONSTRUCTORS                                    *
*******************************************************************************/
	public IPD()
	{
		name = "Iterated Prisoner's Dilemma";
		
		this.evaluationMethod = EvaluationMethod.METHOD_1;
		this.isIndividualTournaments = true;
		this.tournamentRounds = 5;
		this.pdStepCount = 200;
		this.staticStratToBestMap = new HashMap<Strategy, Double>();
		
		this.staticStratToBestMap.put(new StrategyAlwaysCooperate(), 0.0);
		this.staticStratToBestMap.put(new StrategyAlwaysDefect(), 0.0);
		this.staticStratToBestMap.put(new StrategyRandom(), 0.0);
		this.staticStratToBestMap.put(new StrategySimpleEvolved(), 0.0);
		this.staticStratToBestMap.put(new StrategyTitForTat(), 0.0);
		this.staticStratToBestMap.put(new StrategyTitForTwoTats(), 0.0);
	}

/*******************************************************************************
*                                MEMBER METHODS                                *
*******************************************************************************/

//  COMPUTE A CHROMOSOME'S RAW FITNESS *************************************
        
	public void doRawFitness(Chromo X)
	{
		if (this.evaluationMethod == EvaluationMethod.METHOD_1)
		{
            //This code runs for each StrategyEvolved and computes its rawFitness
            
                //New strategy instances need to be created each time we do an 
                //evaluation because move statistics need to be reset between IteratedPD runs.
                List<Strategy> strategies = new ArrayList<Strategy>();
                    strategies.add(new StrategyAlwaysCooperate());
                    strategies.add(new StrategyAlwaysDefect());
                    strategies.add(new StrategyRandom());
                    strategies.add(new StrategyTitForTat());
                    strategies.add(new StrategyTitForTwoTats());
                    
                //Add some random StrategyEvolved
                for (int i = 0; i < 1; i++)
                {
                    int index = Search.r.nextInt(Search.member.length-1);
                    strategies.add(new StrategyEvolved(Search.member[index].chromo));
                }

                //Add the rest of the population
//                for (int i = 0; i < Search.member.length; i++)
//                {
//                    strategies.add(new StrategyEvolved(Search.member[i].chromo));
//                }

                //Do IteratedPD for StrategyEvolved vs all others
                Strategy strategy1 = new StrategyEvolved(X.chromo);
                for (int i = 0; i < strategies.size(); i++)
                {
                    Strategy strategy2 = strategies.get(i);
                    IteratedPD ipd = new IteratedPD(strategy1, strategy2);
                    ipd.runSteps(this.pdStepCount);
                    X.rawFitness += ipd.player1Score();
                }

                //Calculate maximum possible score
                int maxScore = this.pdStepCount * strategies.size() * 7;

                //Nomralize member score
                X.rawFitness = (int)((1000 * X.rawFitness/maxScore)); //score is out of 1000
		}
		else if (this.evaluationMethod == EvaluationMethod.METHOD_2)
		{	
			//Will compute everyone's fitness at one time once per generation
			if (X.rawFitness != 0) {return;}
			
			Map<Strategy, Chromo> strategyToChromoMap = new HashMap<Strategy, Chromo>();
			Map<Chromo, Strategy> chromoToStrategyMap = new HashMap<Chromo, Strategy>();
			List<Strategy> allStrategyList = new ArrayList<Strategy>(); 
			List<Chromo> staticChromoList = new ArrayList<Chromo>();
			
			//Add set of static strategies for comparison purposes
			for (Strategy strat : this.staticStratToBestMap.keySet())
			{
				allStrategyList.add(strat);
			}
			
			//Construct temporary Chromos for static strats
			for (Strategy strat : allStrategyList)
			{
				Chromo c = new Chromo();
				staticChromoList.add(c);
				chromoToStrategyMap.put(c, strat);
				strategyToChromoMap.put(strat, c);
			}
			
			if (isIndividualTournaments)
			{
				//Grab strategy of only this individual
				Strategy strat = IPD.chromoValueToStrategy(X.chromo);
				allStrategyList.add(strat);
				strategyToChromoMap.put(strat, X);
			}
			else
			{
				//Grab strategy of every member in the population
				for (Chromo member : Search.member)
				{
					Strategy strat = IPD.chromoValueToStrategy(member.chromo);
					allStrategyList.add(strat);
					strategyToChromoMap.put(strat, member);
				}
			}
			
			//Compete every member of the population + static set of strats
			for (int i=0; i<allStrategyList.size(); i++)
			{
				Strategy strategy1 = allStrategyList.get(i);
				
				for (int j=i; j<allStrategyList.size(); j++)
				{
					Strategy strategy2 = allStrategyList.get(j);
					
					for (int k=0; k<tournamentRounds; k++)
					{
						Strategy tempStrat1 = IPD.cloneStrategy(strategy1);
						Strategy tempStrat2 = IPD.cloneStrategy(strategy2);
						
						IteratedPD ipd = new IteratedPD(tempStrat1, tempStrat2);
						ipd.runSteps(this.pdStepCount);
                        
                        //Print strategy evolved parameter settings
//                        if(strategy1 instanceof StrategyEvolved && strategy2 instanceof StrategyEvolved)
//                        {
//                            ((StrategyEvolved)strategy1).printParameterSettings((StrategyEvolved)strategy2);
//                            System.out.println(((StrategyEvolved)strategy1).defectCount);
//                        }
//                        else if (strategy1 instanceof StrategyEvolved)
//                            ((StrategyEvolved)strategy1).printParameterSettings();
//                        else if (strategy2 instanceof StrategyEvolved)
//                            ((StrategyEvolved)strategy2).printParameterSettings();

						//Must check b/c static strats have no assocaited chromo
						if (strategyToChromoMap.containsKey(strategy1))
						{
							Chromo c1 = strategyToChromoMap.get(strategy1);
							c1.rawFitness += ipd.player1Score();
						}
						
						if (strategyToChromoMap.containsKey(strategy2))
						{
							Chromo c2 = strategyToChromoMap.get(strategy2);
							c2.rawFitness += ipd.player2Score();			
						}
					}
				}
			}
			
			//Calculate maximum possible score
			int maxScore = 
				(this.tournamentRounds * this.pdStepCount)    //Total PD steps 
				* (allStrategyList.size() + 1) //Times PD run + 1 for playing self
				* 7;                                          //max score for a step
			
			//Nomralize memeber scores where 1000 is highest possible
			if (isIndividualTournaments)
			{
				X.rawFitness = (X.rawFitness / maxScore) * 1000;
			}
			else
			{
				for (Chromo member : Search.member)
				{
					member.rawFitness = (member.rawFitness / maxScore) * 1000;
				}
			}
			
			for (Chromo c : staticChromoList)
			{
				c.rawFitness = (c.rawFitness / maxScore) * 1000;
				Strategy strat = chromoToStrategyMap.get(c);
				
				if (c.rawFitness > this.staticStratToBestMap.get(strat))
				{
					this.staticStratToBestMap.put(strat, c.rawFitness);
				}
			}
		}
	}

//  PRINT OUT AN INDIVIDUAL GENE TO THE SUMMARY FILE *********************************

	public void doPrintGenes(Chromo X, FileWriter output) throws java.io.IOException
	{
            
	}

	public static StrategyEvolved chromoValueToStrategy(String chromoValue)
	{
		if (chromoValue.length() != StrategyEvolved.totalBitCount)
		{
			System.out.println("ERROR: Invalid chromo string");
		}

		return new StrategyEvolved(chromoValue);
	}
	
	public List<StrategyEvolved> chromoValueToStrategies(String chromoValue)
	{
		List<StrategyEvolved> strategeyList = new ArrayList<StrategyEvolved>();
		int start = 0;
		int end = start + StrategyEvolved.totalBitCount;
		
		while (end <= chromoValue.length())
		{
			strategeyList.add(new StrategyEvolved(chromoValue.substring(start, end)));
			start += StrategyEvolved.totalBitCount;
			end += StrategyEvolved.totalBitCount;
		}
		
		return strategeyList;
	}
/*******************************************************************************
*                             STATIC METHODS                                   *
*******************************************************************************/

	//From: http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java?answertab=votes#2581754
    public static <K, V extends Comparable<? super V>> Map<K, V> 
        sortByValue( Map<K, V> map )
    {
        List<Map.Entry<K, V>> list =
            new LinkedList<Map.Entry<K, V>>( map.entrySet() );
        Collections.sort( list, new Comparator<Map.Entry<K, V>>()
        {
            public int compare( Map.Entry<K, V> o1, Map.Entry<K, V> o2 )
            {
                return (o2.getValue()).compareTo( o1.getValue() );
            }
        } );

        Map<K, V> result = new LinkedHashMap<K, V>();
        for (Map.Entry<K, V> entry : list)
        {
            result.put( entry.getKey(), entry.getValue() );
        }
        return result;
    }
    
    public static Strategy cloneStrategy(Strategy strat)
    {
    	Strategy ret = null;
    	
    	if (strat instanceof StrategyAlwaysCooperate)
    	{
    		ret = new StrategyAlwaysCooperate();
    	}
    	else if (strat instanceof StrategyAlwaysDefect)
    	{
    		ret = new StrategyAlwaysDefect();
    	}
    	else if (strat instanceof StrategyEvolved)
    	{
    		StrategyEvolved myStrat = (StrategyEvolved)strat;
    		
    		ret = new StrategyEvolved(myStrat.optimism, myStrat.forgiveness, myStrat.penalty, myStrat.niceness);
    	}
    	else if (strat instanceof StrategyRandom)
    	{
    		ret = new StrategyRandom();
    	}
    	else if (strat instanceof StrategySimpleEvolved)
    	{
    		StrategySimpleEvolved myStrat = (StrategySimpleEvolved)strat;
    		
    		ret = new StrategySimpleEvolved(myStrat.niceness);
    	}
    	else if (strat instanceof StrategyTitForTat)
    	{
    		ret = new StrategyTitForTat();
    	}
    	else if (strat instanceof StrategyTitForTwoTats)
    	{
    		ret = new StrategyTitForTwoTats();
    	}
    	else
    	{
    		System.out.println("ERROR: Could not clone " + strat.name);
    	}
    	
    	return ret;
    }
}

